//
//  BNTabView.swift
//  BNUQuoteTrade
//
//  Created by bebull on 2021/12/22.
//

import UIKit


protocol BNTabViewDelegate: NSObjectProtocol {

    func tabView(_ tabView: BNTabView, didSelectedItemAt index: Int, with scrolling: Bool)
    /// 通知pageview的contentScrollView，需要发生偏移
    func contentScrollView(offsetAt index: Int)

}

class BNTabView: UIView {

    // MARK: - Properties
    // MARK: Public
    var titleItems: [BNTabTitleItem] = []
    
    var selectedIndex : Int = 0
    
    weak var delegate: BNTabViewDelegate?
    /// tabPabeView使用
    var needLayout: Bool = true

    // MARK: Private
    private lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal

        let view = UICollectionView(frame: self.bounds, collectionViewLayout: layout)
        view.backgroundColor = .clear
        view.showsHorizontalScrollIndicator = false
        view.showsVerticalScrollIndicator = false
        view.dataSource = self
        view.delegate = self
        if #available(iOS 11.0, *) {
            view.contentInsetAdjustmentBehavior = .never
        }
        view.register(BNTabItemView.self, forCellWithReuseIdentifier: NSStringFromClass(BNTabItemView.self))
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        return view
    }()
    private lazy var bottomlineView = UIView()
    private var layout: BNTabLayout
    private var items: [BNTabItem] = []
    private var lrMargin: CGFloat = 0.0
    private var tabMargin: CGFloat = 0.0
    private var showBadgeIndexs: NSMutableSet = NSMutableSet()

    // MARK: - Init

     init(frame: CGRect, with layout: BNTabLayout) {
         self.layout = layout
        super.init(frame: frame)
         createViews()
    }

    convenience override init(frame: CGRect) {
        self.init(frame: frame, with: BNTabLayout.defaultLayout())
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    deinit {
    }

    // MARK: - Override

    override func layoutSubviews() {
        super.layoutSubviews()
        if needLayout {
            needLayout = false
            reloadData()
        }
    }

    // MARK: - Publics
    public func reloadData() {
        /// 构造 [BNTabItem] 数组
        refreshItems()
        
        refreshState()
        collectionView.collectionViewLayout.invalidateLayout()
        collectionView.reloadData()
    }

    /// 展示index的badge
    public func showBadge(at index: Int) {
        showBadgeIndexs.add(index)
        collectionView.reloadData()
    }

    public func hideBadge(at index: Int) {
        if let itemView = collectionView.cellForItem(at: IndexPath(row: index, section: 0)) as? BNTabItemView {
            itemView.hideRedDot = true
        }
        showBadgeIndexs.remove(index)
    }
    
    public func contentScrollViewDidChanged(_ contentOffset: CGPoint, width: CGFloat) {
        
        let viewWidth: CGFloat = width
        var ratio: CGFloat = contentOffset.x / viewWidth
        if ratio > CGFloat(items.count - 1) || ratio < 0 {
            return
        }
        if contentOffset.x == 0 , selectedIndex == 0 {
            return
        }
        if contentOffset.x + viewWidth == viewWidth,
           selectedIndex == items.count - 1 {
            return
        }
        
        ratio = max(0, min(CGFloat(items.count - 1), ratio))
        let baseIndex: Float = floorf(Float(ratio))
        let remainderRatio: CGFloat = ratio - CGFloat(baseIndex)

        contentScrollViewDidScroll(with: Int(baseIndex), percent: remainderRatio)

        if abs(ratio - CGFloat(selectedIndex)) > 0.5 {
            var targetIndex = baseIndex
            if ratio < CGFloat(selectedIndex) {
                targetIndex = baseIndex
            }
            if ratio > CGFloat(selectedIndex) {
                targetIndex = baseIndex + 1
            }
            selectItem(at: Int(targetIndex), with: true)
        }
    }
}

extension BNTabView {

    // MARK: - Notifications

    // MARK: - Action

    // MARK: - Privates

    private func createViews() {
        addSubview(collectionView)

        bottomlineView.backgroundColor = layout.lineColor
        bottomlineView.isHidden = layout.lineHidden
        if layout.lineHeight != 2 {
            collectionView.insertSubview(bottomlineView, at: 0)
        } else {
            collectionView.addSubview(bottomlineView)
        }

    }

    private func selectItem(at targetIndex: Int) {
        selectItem(at: targetIndex, with: false)
    }

    private func selectItem(at targetIndex: Int, with scrolling: Bool) {
        let oldItem = items[selectedIndex]
        let targetItem = items[targetIndex]
        oldItem.selected = false
        targetItem.selected = true

        if let oldItemView = collectionView.cellForItem(at: IndexPath(row: selectedIndex, section: 0)) as? BNTabItemView {
            oldItemView.reload(item: oldItem)
        }

        if let targetItemView = collectionView.cellForItem(at: IndexPath(row: targetIndex, section: 0)) as? BNTabItemView {
            targetItemView.reload(item: targetItem)
        }
        collectionView.scrollToItem(at: IndexPath(item: targetIndex, section: 0), at: .centeredHorizontally, animated: true)

        if scrolling == false {
            let targetItemFrame = getItemViewFrame(targetIndex)
            let lineWidth = items[targetIndex].lineWidth
            let lineX = targetItemFrame.origin.x + (targetItemFrame.size.width - lineWidth) / 2.0

            if layout.lineScrollDisable {
                var frame = bottomlineView.frame
                frame.origin.x = lineX
                frame.size.width = lineWidth
                bottomlineView.frame = frame
            } else {
                UIView.animate(withDuration: 0.25, animations: { [weak self] in
                    guard let self = self else { return }
                    var frame = self.bottomlineView.frame
                    frame.origin.x = lineX
                    frame.size.width = lineWidth
                    self.bottomlineView.frame = frame
                })
            }
        }
        selectedIndex = targetIndex
        delegate?.tabView(self, didSelectedItemAt: targetIndex, with: scrolling)
    }

    private func getItemViewFrame(_ index: Int) -> CGRect {
        if index >= items.count {
            return .zero
        }
        var x = lrMargin
        for i in 0..<index {
            let item = items[i]
            x += item.width + tabMargin
        }
        let width = items[index].width
        return CGRect(x: x, y: 0, width: width, height: bounds.size.height)
    }
    
    /// 构造 [BNTabItem] 数组
    private func refreshItems() {
        var array = [BNTabItem]()
        for (idx, titleItem) in titleItems.enumerated() {
            var width = layout.tabWidth
            var lineWidth = layout.lineWidth
            if abs(width) < CGFloat.leastNormalMagnitude {
                var font = titleItem.titleFont
                if idx == selectedIndex {
                    font = titleItem.titleSelectedFont
                }
                
                let attributes = [NSAttributedString.Key.font: font]
                let option = NSStringDrawingOptions.usesLineFragmentOrigin
                if let title = titleItem.title {
                    let rect: CGRect = title.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: bounds.size.height), options: option, attributes: attributes, context: nil)
                    width = rect.width + layout.tabPadding * 2
                }
            }
            if abs(lineWidth) < CGFloat.leastNormalMagnitude {
                lineWidth = width
            }

            let item = BNTabItem()
            item.width = width
            item.lineWidth = lineWidth
            
            item.color = layout.tabColor
            item.selectedColor = layout.tabSelectedColor
            
            item.title = titleItem.title
            item.titleFont = titleItem.titleFont
            item.titleSelectedFont = titleItem.titleSelectedFont
            item.titleColor = titleItem.titleColor
            item.titleSelectedColor = titleItem.titleSelectedColor
            
            if idx == selectedIndex {
                item.selected = true
            } else {
                item.selected = false
            }
            array.append(item)
        }
        items = array
    }

    private func refreshState() {
        if items.count < 1 {
            return
        }
        if selectedIndex >= items.count {
            selectedIndex = 0
        }

        var totalWidth = layout.lrMargin
        var totalItemWidth: CGFloat = 0
        for (_, obj) in items.enumerated() {
            totalItemWidth += obj.width
            totalWidth += obj.width + self.layout.tabMargin
        }
        totalWidth -= layout.tabMargin
        totalWidth += layout.lrMargin

        if totalWidth < bounds.size.width && layout.leftAlign == false {
            var spacing: CGFloat = 0
            if items.count > 0 {
                if layout.tabMargin == 0 {
                    lrMargin = layout.lrMargin
                    spacing = (bounds.size.width - totalItemWidth - lrMargin * 2) / CGFloat(items.count - 1)
                } else {
                    spacing = (bounds.size.width - totalItemWidth) / CGFloat(items.count)
                    lrMargin = spacing / 2.0
                }
            }
            tabMargin = spacing
        } else if totalWidth > bounds.size.width && layout.leftAlign == true {

            var spacing: CGFloat = 0
            if items.count > 0 {
                lrMargin = layout.lrMargin
                spacing = (bounds.size.width - totalItemWidth - lrMargin * 2) / CGFloat(items.count - 1)
                if spacing < 0 {
                    spacing = layout.tabMargin
                }
            }
            tabMargin = spacing
        } else {
            lrMargin = layout.lrMargin
            tabMargin = layout.tabMargin
        }

        var selectedItemX = lrMargin
        var selectedItemWidth: CGFloat = 0
        totalWidth = lrMargin

        for (idx, obj) in items.enumerated() {
            if idx < self.selectedIndex {
                selectedItemX += obj.width + self.tabMargin
            } else if idx == self.selectedIndex {
                selectedItemWidth = obj.width
            }
            totalWidth += obj.width + self.tabMargin
        }

        totalWidth -= layout.tabMargin
        totalWidth += layout.lrMargin

        let minX: CGFloat = 0
        let maxX: CGFloat = totalItemWidth - self.bounds.size.width
        let targetX: CGFloat = selectedItemX - self.bounds.size.width / 2.0 + selectedItemWidth / 2.0
        collectionView.setContentOffset(CGPoint(x: max(min(maxX, targetX), minX), y: 0), animated: false)

        self.delegate?.contentScrollView(offsetAt: selectedIndex)
        
        
        let lineWidth = items[selectedIndex].lineWidth
        bottomlineView.frame = CGRect(x: selectedItemX + (items[selectedIndex].width - lineWidth) / 2, y: self.bounds.size.height - layout.lineHeight - layout.linePadding, width: lineWidth, height: layout.lineHeight)

        bottomlineView.layer.cornerRadius = layout.lineCornerRadius
    }

    private func contentScrollViewDidScroll(with leftIndex: Int, percent: CGFloat) {
        var perc = percent
        let rightIndex = leftIndex + 1
        if rightIndex >= items.count {
            return
        }

        let leftItemFrame = getItemViewFrame(leftIndex)
        let rightItemFrame = getItemViewFrame(rightIndex)

        var lineWidth: CGFloat = items[leftIndex].lineWidth
        var lineX: CGFloat = leftItemFrame.origin.x + (leftItemFrame.size.width - lineWidth) / 2.0
        if perc > CGFloat.leastNormalMagnitude {
            let rightWidth: CGFloat = items[rightIndex].lineWidth
            let rightX: CGFloat = rightItemFrame.origin.x + (rightItemFrame.size.width - rightWidth) / 2.0
            perc = min(1, perc)
            lineX = lineX + (rightX - lineX) * perc
            lineWidth = lineWidth + (rightWidth - lineWidth) * perc
        }
        var frame = bottomlineView.frame
        frame.origin.x = lineX
        frame.size.width = lineWidth
        bottomlineView.frame = frame
    }
}



extension BNTabView: UICollectionViewDelegate {

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        
        guard let itemView = cell as? BNTabItemView else { return }
        itemView.reload(item: items[indexPath.item])
        
        itemView.hideRedDot = true
        showBadgeIndexs.enumerateObjects { (index, _) in
            if let inde = index as? Int, indexPath.row == inde {
                itemView.hideRedDot = false
                return
            }
        }
    }
}

extension BNTabView: UICollectionViewDataSource {
    
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        items.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        return collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(BNTabItemView.self), for: indexPath)
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        selectItem(at: indexPath.item)
        self.delegate?.contentScrollView(offsetAt: selectedIndex)
    }
    
}

extension BNTabView: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: 0, left: lrMargin, bottom: 0, right: lrMargin)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let item = items[indexPath.item]
        return CGSize(width: item.width, height: bounds.size.height)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return tabMargin
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return tabMargin
    }
    
}

